Previous Book Contents Book Index Next

Inside Macintosh: QuickDraw GX Programmer's Overview / Part 2 - The QuickDraw GX Programming Cookbook
Chapter 7 - Handling Typography


Hit-Testing Layout Shapes

This programming recipe provides feedback when the user presses the
mouse button on a layout shape. If the user simply clicks on the layout shape, indicating a caret selection, the code in this recipe draws a caret, as shown
in Figure 7-1.

Figure 7-1 Sample caret selection

If the user drags the mouse throughout the layout shape, the code in this recipe provides selection feedback, tracking the mouse and allowing the user to create range selections, as shown in Figure 7-2.

Figure 7-2 Sample range selection

This recipe makes extensive use of the QuickDraw GX selection library and the QuickDraw GX layout edit library. These libraries contain data types and functions that help you provide selection feedback for your application's users. In fact, all of the functionality described in this recipe--and much more--is implemented for you in those libraries.

Here are a few key concepts you need to understand before beginning
this recipe:

Note
The examples given above describe a simple selection and highlight shape--you can use the layout shape and the layout edit library to provide sophisticated features, such as multiple-range selections, discontiguous highlighting, and slanted highlighting.<8bat>u

Overview of Recipe Steps

The steps in this recipe show you how to:

    1. Determine if a layout shape was hit
    2. Create a layout edit structure
    3. Hit-test the layout shape
    4. Erase any previous highlight
    5. Make a new selection and highlight
    6. Provide selection feedback while the user drags the mouse
    7. Determine if the mouse has moved to a new offset
    8. Determine if the selection has changed from a caret to a highlight
    9. Redraw the caret or highlight
    10. Draw the difference between the old and new highlights

You need to follow the instructions in each of these steps to verify a hit-test on a layout shape and to provide layout selection feedback.

Functions Used in This Recipe

QuickDraw GX functions used in this recipe:
GXHitTestPicture"Picture Shapes"
QuickDraw GX Graphics
GXGetShapeType"Shape Objects"
QuickDraw GX Objects
GXHitTestLayout"Layout Carets, Highlighting,
and Hit-Testing"
QuickDraw GX Typography
GXDrawShape"Shape Objects"
QuickDraw GX Objects
GXGetShapeViewPorts"Transform Objects"
QuickDraw GX Objects
GXGetViewPortMouse"QuickDraw GX and the
Macintosh Environment"
QuickDraw GX Environment and Utilities
GXCopyToShape"Shape Objects"
QuickDraw GX Objects
GXExcludeShape"Geometric Operations"
QuickDraw GX Graphics
GXDisposeShape"Shape Objects"
QuickDraw GX Objects

This recipe gives a brief description of these functions; you can find complete reference information for them in the Inside Macintosh suite of books.

This recipe also uses functions from the QuickDraw GX libraries:
LayoutEditHandleFromLayoutlayout edit library
LockEditHandlelayout edit library
NewSelectionAndHighlightlayout edit library

Recipe Step Descriptions

In this section, each step is described individually.

  1. Determine if a layout shape was hit

    Before you begin providing selection feedback, you need to determine if
    the user hit a layout shape and, if so, which layout shape was hit. If your layout shapes are stored in a picture with other shapes, you can use the GXHitTestPicture function to determine which shape was hit, as you
    did when you hit-tested graphics shapes in the last chapter. As you did in that chapter, you must first convert the hit point from QuickDraw global coordinates to QuickDraw GX local coordinates and store the resulting QuickDraw GX point structure in a variable named originalPoint. Then you can call GXHitTestPicture to hit-test the picture:

    hitShape = GXHitTestPicture(gCurrent->picture, 
    originalPoint,
    &result, 1, 1);

    You can determine whether the hit shape was a layout shape by examining its type using the GXGetShapeType function. If the expression

    GXGetShapeType(hitShape) == gxLayoutType

    returns true, a layout shape was hit, and you can proceed to Step 2.

  2. Create a layout edit structure

    The layout edit library provides a layout edit structure for you to use to store information about a layout shape's current selection and the current high-
    light shape.

    This step shows you how to create a layout edit structure. Later in this recipe, you use these fields of the structure:

    • The layout field contains a reference to the layout shape containing
      the selection.
    • The highlight field contains a reference to the highlight shape.

      You use the LayoutEditHandle type, provided in the layout edit library, to declare a handle to a layout edit structure:

      LayoutEditHandle myLayoutEditHandle;

      When the user presses the mouse button on a layout shape, you could use this if statement to create a layout edit structure:

      if (MyLayoutAlreadyHasSelection(hitShape)) 
      myLayoutEditHandle = MyLookupLayoutEdit(hitShape);
      else
      myLayoutEditHandle = LayoutEditHandleFromLayout(hitShape);

      In your MyLayoutAlreadyHasSelection function, you could determine
      if you've already created a layout edit structure for the layout shape that was hit and in your MyLookupLayoutEdit function you could return the previously created layout edit handle;

      As a simple example, if your application allows selection in a single layout shape at a time, you could store a reference to the currently selected layout shape and the handle to its layout edit structure in global variables. You could then implement your MyLayoutAlreadyHasSelection function with this line of code:

      return (hitShape == gCurrentlySelectedLayout);

      and your MyLookupLayoutEdit function with this line of code:

      return gCurrentLayoutEditHandle;

      If you determine that the layout shape that the user hit does not
      already have a layout edit structure, you call the library function LayoutEditHandleFromLayout to create one.

      While the user is dragging the mouse and you're providing selection feed-
      back, you should lock the layout edit structure in memory and use a pointer to reference it. You can use the library function LockEditHandle to lock
      the layout edit structure in memory. This function returns a pointer to
      the structure:

      LayoutEditPtr myLayoutEdit;

      myLayoutEdit = LockEditHandle(myLayoutEditHandle);

      Most of the layout edit library functions use layout edit pointers rather than layout edit handles.

Note
The LayoutEditPtr data type is one of the data types used internally by the layout edit library because normally you don't have to know the internal structure of the layout edit structure to use the layout edit library. However, the purpose of this recipe is to introduce
how the layout edit library implements some of its commonly used functions. To follow the steps of this recipe, you need to include the definition of the layout edit structure in your application, which you can do simply by including the layout edit library.c file.<8bat>u
  1. Hit-test the layout shape

    Now that you have prepared your layout edit structure, you're ready to hit-test the layout. You use the GXHitTestLayout function, which returns information in a layout hit-test information structure, as defined by the gxLayoutHitInfo data type. Before you call GXHitTestLayout, you need
    to declare such a structure to store the results:

    gxLayoutHitInfo hitInfo;

    Then you can call GXHitTestLayout to determine information about
    the hit-test:

    GXHitTestLayout(myLayoutEdit->layout, &originalPoint,
    gxHighlightAverageAngle, &hitInfo, nil);

    This function examines the hit point and analyzes the layout shape to determine the hit offset--that is, where in the text the user pressed on the mouse. As a simple example, if the text of the layout is "Layout Shape" and the user clicked the mouse between the "y" glyph and the "o" glyph, this function calculates that the hit offset into the layout text is 3--indicating the layout was hit between the third and fourth characters in the text.

    The GXHitTestLayout function returns the hit offset in the hitSideOffset field of the layout hit-test information structure. The selection library provides the SelectionOffset type, which you can use to store the offset:

    SelectionOffset originalOffset;

    originalOffset = (SelectionOffset) hitInfo.hitSideOffset;

    You use this offset when providing selection feedback in Step 6.

  2. Erase any previous highlight

    If your layout shape already had a highlighted selection, you need to dehighlight the old selection by erasing the highlight shape stored in the highlight field of the layout edit structure. The layout edit library uses a transfer mode for highlight shapes that is similar to the exclusive-OR mode you used in the last chapter to provide feedback when the user dragged the mouse. As a result, you can dehighlight the previous selection simply by calling drawing the highlight shape, thus:

    if (myLayoutEdit->highlight != nil)
    GXDrawShape(myLayoutEdit->highlight);
  3. Make a new selection and highlight

    When the user presses the mouse button on your layout shape, the selection and highlight shape both change, so you need to update the information in your layout edit structure.

    The library function NewSelectionAndHighlight stores the new selection and uses it to update the highlight shape. To do this, you can call:

    NewSelectionAndHighlight(myLayoutEdit, 
    originalOffset,
    originalOffset);

    This function takes three parameters:

    • The first parameter specifies the layout edit structure.
    • The second parameter specifies the beginning offset of the selection.
    • The third parameter specifies the final offset of the selection.

      Since you specify the hit offset as both the beginning offset and the final offset, this function creates a caret selection and a caret highlight. You can draw the caret by drawing the highlight shape:

      GXDrawShape(myLayoutEdit->highlight);

  4. Provide selection feedback while the user drags the mouse

    To provide selection feedback while the user drags the mouse, you
    can repeatedly call the GXGetViewPortMouse function to find the current mouse position, and then redraw the selection accordingly. To use the GXGetViewPortMouse function, you need a reference to the view port. You can store a reference to the view port in a global variable, in your document information structure, or you can obtain it directly from your layout shape:

    gxViewPort layoutViewPort;

    GXGetShapeViewPorts(myLayoutEdit->layout, &layoutViewPort);

    Here is the flow of control for the selection feedback loop:

    gxPoint oldPoint, newPoint;
    SelectionOffset oldOffset, newOffset;

    oldPoint.x = originalPoint.x;
    oldPoint.y = originalPoint.y;

    oldOffset = originalOffset;

    while (Button()) {

        GXGetViewPortMouse(layoutViewPort, &newPoint);

        if (MouseMoved(newPoint, oldPoint)) {
    /* Draw new selection as appropriate. See Steps 7-10. */
    }
    }

    The following steps--Steps 7 through 10--show how to give feedback if the mouse has moved since the last time through the loop.

  5. Determine if the mouse has moved to a new offset

    Even after you've determined that the mouse has moved, you still need to determine whether it moved enough to change the selection. You can use the GXHitTestLayout function to determine the new offset implied by the mouse location:

    GXHitTestLayout(myLayoutEdit->layout, &newPoint,
    gxHighlightAverageAngle, &hitInfo, nil);

    newOffset = hitInfo.hitSideOffset;

    and then compare the new offset with the offset from the previous time through the loop:

    if (newOffset != oldOffset) {
    /* Highlight new selection. See Steps 8-10. */
    }

    The following steps, Steps 8 through 10, show how to change the selection to reflect the new mouse position.

  6. Determine if the selection has changed from a caret to a highlight

    To change the highlighted selection, you could simply erase the old high-
    light shape, calculate the new highlight shape, and draw the new highlight shape. In fact, this is exactly what you do if the selection is changing from
    a caret selection to a range selection, or from a range selection to a
    caret selection.

    However, if the selection is changing from one range selection to another, the old range and the new range probably overlap. If you erase the entire old highlight and draw the new highlight shape, the user will see a flicker when dragging the mouse, so you need to handle this case differently.

    First, however, you have to determine if the selection is changing from a caret to a range or vice versa. You can use these variables to save informa-
    tion about the type of selection:

    boolean oldWasCaret = true,
    newIsCaret = true;

    Then, to test if the selection type is changing, you can use this code:

    newIsCaret = (newOffset == originalOffset);

    if (oldWasCaret != newIsCaret) {
    /* Redraw entire highlight. See Step 9. */
    } else {
    /* Draw the difference between highlights. See Step 10. */
    }

    The next two steps show how to update the highlight for these two cases.

  7. Redraw the caret or highlight

    To redraw the entire highlight shape, you simply draw the old shape to erase it, calculate the new highlight shape using the library function NewSelectionAndHighlight, and then draw the new highlight shape.
    Here is the code:

    GXDrawShape(myLayoutEdit->highlight); /* erase old */
    NewSelectionAndHightlight(myLayoutEdit,
    originalOffset,
    newOffset);
    GXDrawShape(myLayoutEdit->highlight); /* draw new */
  8. Draw the difference between the old and the new highlight

    If the selection is changing from one range to another, you only need to draw the difference between the old highlight shape and the new highlight shape. You can use the QuickDraw GX function GXExcludeShape to find the difference between the two highlight shapes.

    You'll need to store references to the old highlight shape and to a shape representing the differences between the two highlight shapes:

    gxShape oldHighlight = nil,
    differenceHighlight = nil;

    Here is the code:

    oldHighlight = GXCopyToShape(oldHighlight,
    myLayoutEdit->highlight);

    NewSelectionAndHighlight(myLayoutEdit,
    originalOffset,
    newOffset);

    differenceHighlight = GXCopyToShape(differenceHighlight,
    myLayoutEdit->highlight);

    GXExcludeShape(differenceHighlight, oldHighlight);

    GXDrawShape(differenceHighlight);

    When you finish using the two highlight shapes, dispose of them:

    GXDisposeShape(differenceHighlight);
    GXDisposeShape(oldHighlight);

    Finally, after you've updated the highlight, set up the variables for the next time through the loop:

    oldPoint.x = newPoint.x;
    oldPoint.y = newPoint.y;

    oldOffset = newOffset;

    oldWasCaret = newIsCaret;

Related Recipes

The next recipe, "Text Editing," shows how you can allow the user to edit text in a layout shape. The code in that recipe responds to key presses by deleting the selected text from a layout and inserting the specified character.

The recipes in Chapter 4, "Using the QuickDraw GX Environment," show you how to initialize QuickDraw GX. You should read the recipes in that chapter before using any recipes in this chapter.

The recipes in Chapter 5, "Using Macintosh Windows," show you how to
use QuickDraw GX with Macintosh windows. You need to be familiar with
the information in that chapter before you can display layout shapes in a Macintosh window.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
8 JUL 1996